package ru.mirent;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.time.LocalDateTime;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import static org.mockito.internal.verification.VerificationModeFactory.times;

/**
 * Создание заглушек для статичных методов
 */
public class MockitoStaticMethodsTest {

    /**
     * Пример создания заглушки для void статичного метода класса.
     */
    @Test
    void callVoidMethodOneTest() {
        MockedStatic<TestClassStatic> mock = mockStatic(TestClassStatic.class);
        // Создание заглушки статичного класса
        mock
                // Объявление метода, для которого создается заглушка
                .when(() -> TestClassStatic.print(anyString()))
                // Настройка ответа для вызванного метода
                .thenAnswer((Answer<Void>) invocation -> {
                            // Получение списка переданных в метод параметров
                            Object[] args = invocation.getArguments();
                            String param = args[0].toString();
                            System.out.println("Вызван замокированный метод с параметром: " + param);
                            return null;
                        }
                );

        // Вызов тестируемого метода
        TestClassStatic.print("Test Text");

        // Проверка вызова метода с требуемыми параметрами
        mock.verify(() -> TestClassStatic.print("Test Text"), times(1));

        // Завершение работы заглушки
        mock.close();
    }

    /**
     * Вариация метода {@link #callVoidMethodOneTest()}, рекомендованный способ создания заглушки статичного класса
     */
    @Test
    void callVoidMethodTwoTest() {
        // Создание заглушки статичного класса
        try (MockedStatic<TestClassStatic> mock = mockStatic(TestClassStatic.class)) {
            mock
                    // Объявление метода, для которого создается заглушка
                    .when(() -> TestClassStatic.print(anyString()))
                    // Настройка ответа для вызванного метода
                    .thenAnswer((Answer<Void>) invocation -> {
                        // Получение списка переданных в метод параметров
                        Object[] args = invocation.getArguments();
                        String param = args[0].toString();
                        System.out.println("Вызван замокированный метод с параметром: " + param);
                        return null;
                    });

            // Вызов тестируемого метода
            TestClassStatic.print("Test Text");

            // Проверка вызова метода с требуемыми параметрами
            mock.verify(() -> TestClassStatic.print("Test Text"), times(1));
        }
    }

    /**
     * Вариация метода {@link #callVoidMethodOneTest()} с развернутым созданием объектов
     */
    @Test
    void callVoidMethodThreeTest() {
        MockedStatic<TestClassStatic> mock = mockStatic(TestClassStatic.class);

        // Создание заглушки статичного класса
        mock
                // Объявление метода, для которого создается заглушка
                .when(new MockedStatic.Verification() {
                    @Override
                    public void apply() throws Throwable {
                        System.out.println("Настройка метода для возврата значения");
                        TestClassStatic.print(anyString());
                    }
                })
                // Настройка ответа для вызванного метода
                .thenAnswer(new Answer<Void>() {
                                public Void answer(InvocationOnMock invocation) throws Throwable {
                                    Object[] args = invocation.getArguments();
                                    String param = args[0].toString();
                                    System.out.println("Вызван замокированный метод с параметром: " + param);
                                    return null;
                                }
                            }
                );

        // Вызов тестируемого метода
        TestClassStatic.print("Test Text");

        // Проверка вызова метода с требуемыми параметрами
        mock.verify(() -> TestClassStatic.print("Test Text"), times(1));

        // Завершение работы заглушки
        mock.close();
    }

    /**
     * Создание заглушки статичного метода класса с возвратом параметров
     */
    @Test
    void callMethodWithReturnParamTest() {
        MockedStatic<TestClassStatic> mock = mockStatic(TestClassStatic.class);

        // Создание заглушки статичного класса
        mock
                // Объявление метода, для которого создается заглушка
                .when(() -> TestClassStatic.getErrorMessage(anyString()))
                // Настройка ответа для вызванного метода
                .thenAnswer((Answer<String>) invocation -> {
                            // Получение списка переданных в метод параметров
                            Object[] args = invocation.getArguments();
                            String param = args[0].toString();
                            System.out.println("Вызван замокированный метод с параметром: " + param);
                            return "New Test Value";
                        }
                );

        // Вызов тестируемого метода
        Assertions.assertEquals("New Test Value", TestClassStatic.getErrorMessage("Test Text"));

        // Проверка вызова метода с требуемыми параметрами
        mock.verify(() -> TestClassStatic.getErrorMessage("Test Text"), times(1));

        // Завершение работы заглушки
        mock.close();
    }

    /**
     * Пример возврата класса-заглушки mockLocalDateTime.getMinute в статичном методе класса LocalDateTime.now
     */
    @Test
    void anotherExampleCallStaticTest() {
        try (MockedStatic<LocalDateTime> mocked = mockStatic(LocalDateTime.class)) {
            var mockLocalDateTime = mock(LocalDateTime.class);
            when(mockLocalDateTime.getMinute()).thenReturn(30);
            mocked.when(LocalDateTime::now).thenReturn(mockLocalDateTime);

            LocalDateTime result = LocalDateTime.now();

            Assertions.assertEquals(30, result.getMinute());
        }
    }

    /**
     * Тестовый класс для демонстрации заглушек статичных методов
     */
    private final static class TestClassStatic {
        public static void print(String text) {
            System.out.println("Текст для вывода: " + text);
        }

        public static String getErrorMessage(String message) {
            System.out.println("Сообщение ошибки: " + message);
            return "Сообщение ошибки: " + message;
        }
    }
}
